package com.example.seed.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.babyfish.jimmer.meta.ImmutableProp;
import org.babyfish.jimmer.meta.ImmutableType;
import org.babyfish.jimmer.sql.cache.AbstractCacheFactory;
import org.babyfish.jimmer.sql.cache.Cache;
import org.babyfish.jimmer.sql.cache.CacheFactory;
import org.babyfish.jimmer.sql.cache.caffeine.*;
import org.babyfish.jimmer.sql.cache.chain.*;
import org.babyfish.jimmer.sql.cache.redis.spring.*;
import org.babyfish.jimmer.sql.meta.DatabaseNamingStrategy;
import org.babyfish.jimmer.sql.runtime.DefaultDatabaseNamingStrategy;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;

import java.time.Duration;
import java.util.Arrays;
import java.util.List;

@Configuration
public class JimmerConfig {


    /**
     * 忽略缓存白名单
     */
    private static final List<Class<?>> IGNORING_CACHE_WHITELIST = Arrays.asList(System.class, System.class);

    /**
     * 数据库命名策略
     *
     * @return {@link DatabaseNamingStrategy}
     */
    @Bean
    public DatabaseNamingStrategy databaseNamingStrategy() {
        return DefaultDatabaseNamingStrategy.LOWER_CASE;
    }

    /**
     * 缓存工厂
     *
     * @param connectionFactory 连接工厂
     * @param objectMapper      对象映射器
     * @return {@link CacheFactory}
     */
    @Bean
    public CacheFactory cacheFactory(RedisConnectionFactory connectionFactory,
                                     ObjectMapper objectMapper) {

        return new CacheFactory() {
            @Override
            public Cache<?, ?> createObjectCache(ImmutableType type) {
                //一对一忽略缓存
                if (IGNORING_CACHE_WHITELIST.contains(type.getJavaClass())) {
                    return null;
                }
                return new ChainCacheBuilder<>()
                        .add(
                                RedisValueBinder
                                        .forObject(type)
                                        .redis(connectionFactory)
                                        .objectMapper(objectMapper)
                                        .duration(Duration.ofHours(24))
                                        .build()
                        )
                        .build();
            }

            /**
             * 创建关联 ID 缓存 一对一或多对一关联
             *
             * @param prop 支柱
             * @return {@link Cache }<{@link ? }, {@link ? }>
             */
            @Override
            public Cache<?, ?> createAssociatedIdCache(ImmutableProp prop) {
                return createPropCache(
                        prop,
                        Duration.ofHours(5)
                );
            }

            @Override
            @Nullable
            public Cache<?, List<?>> createAssociatedIdListCache(@NotNull ImmutableProp prop) {
                return createPropCache(
                        prop,
                        Duration.ofHours(5)
                );
            }

            @Override
            @Nullable
            public Cache<?, ?> createResolverCache(@NotNull ImmutableProp prop) {
                return createPropCache(
                        prop,
                        Duration.ofHours(5)
                );
            }

            private <K, V> Cache<K, V> createPropCache(
                    ImmutableProp prop,
                    Duration redisDuration
            ) {
                return new ChainCacheBuilder<K, V>()
                        .add(
                                (SimpleBinder<K, V>) RedisValueBinder
                                        .forProp(prop)
                                        .redis(connectionFactory)
                                        .objectMapper(objectMapper)
                                        .duration(redisDuration)
                                        .build()
                        )
                        .build();
            }
        };


    }

}
